﻿using System;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Aliyun.OSS;
using Newtonsoft.Json;
using Unity.EditorCoroutines.Editor;
using Object = UnityEngine.Object;
using ZyGame.Transport;
using PackageInfo = ZyGame.Resource.PackageInfo;
using HybridCLR.Editor;
using HybridCLR.Editor.Commands;
using static UnityEngine.GraphicsBuffer;

namespace ZyGame.Editor.AssetBuilder
{
    public enum PageState
    {
        Main,
        Cache,
    }

    public class PackageBuilderEditorWindow : EditorWindow
    {
        public PageState state;
        public PackageCacheWindow cache;
        public PackageEditorWindow package;
        private Vector2 pos = Vector2.zero;

        public void Awake()
        {
        }

        public void OnEnable()
        {
            Client.Tools.ClearConroutineStartup();
            Client.Tools.RegisterConroutineStartu(a => this.StartCoroutine(a), b => { });
            cache = new PackageCacheWindow(this);
            package = new PackageEditorWindow(this);
        }

        public void OnDisable()
        {
            Client.Tools.ClearConroutineStartup();
        }

        public void OnGUI()
        {
            if (package is null || cache is null)
            {
                OnEnable();
            }

            EditorGUILayout.BeginVertical();
            GUILayout.BeginHorizontal(EditorStyles.toolbar);
            GUILayout.FlexibleSpace();
            if (GUILayout.Button(state == PageState.Main ? "Cache" : "Packages", EditorStyles.toolbarButton))
            {
                state = state == PageState.Main ? PageState.Cache : PageState.Main;
            }

            if (GUILayout.Button("Options", EditorStyles.toolbarButton))
            {
                SettingsService.OpenProjectSettings("Project/Options");
            }

            if (GUILayout.Button("Build", EditorStyles.toolbarButton))
            {
                GenericMenu menu = new GenericMenu();
                menu.AddItem(new GUIContent("Build All"), false, () =>
                {
                    ModuleInfo[] infos = new ModuleInfo[PackageBuilderSetting.instance.moudles.Count];
                    for (int i = 0; i < infos.Length; i++)
                    {
                        infos[i] = PackageBuilderSetting.instance.moudles[i];
                    }

                    this.package.Build(infos);
                });
                foreach (var item in PackageBuilderSetting.instance.moudles)
                {
                    menu.AddItem(new GUIContent("Build Group/" + item.root.name), false, () => { this.package.Build(item); });
                }

                menu.ShowAsContext();
            }

            GUILayout.EndHorizontal();
            pos = GUILayout.BeginScrollView(pos);
            switch (state)
            {
                case PageState.Main:
                    package?.OnGUI();
                    break;
                case PageState.Cache:
                    cache?.OnGUI();
                    break;
            }

            GUILayout.FlexibleSpace();
            GUILayout.EndScrollView();
            EditorGUILayout.EndVertical();
        }
    }

    public class PackageEditorWindow
    {
        private PackageBuilderEditorWindow builder;
        private Dictionary<string, bool> foldouts = new Dictionary<string, bool>();

        public PackageEditorWindow(PackageBuilderEditorWindow window)
        {
            builder = window;
        }

        public void OnGUI()
        {
            GUILayout.Space(10);
            if (AppConfig.instance.gameOptions.packageRoot.IsNullOrEmpty())
            {
                GUI.Label(new Rect(builder.position.width / 2 - 50, builder.position.height / 2 - 10, 100, 20), "Not Set Package Root Dir");
                return;
            }

            string[] moduels = AssetDatabase.GetSubFolders(AppConfig.instance.gameOptions.packageRoot);
            if (PackageBuilderSetting.instance.moudles is null)
            {
                PackageBuilderSetting.instance.moudles = new List<ModuleInfo>();
            }

            for (int i = 0; i < moduels.Length; i++)
            {
                Object root = AssetDatabase.LoadAssetAtPath(moduels[i], typeof(Object));
                if (PackageBuilderSetting.instance.moudles.Find(x => x.root == root) is null)
                {
                    PackageBuilderSetting.instance.moudles.Add(new ModuleInfo()
                    {
                        root = root,
                        version = 0
                    });
                }
            }

            if (PackageBuilderSetting.instance.moudles is null || PackageBuilderSetting.instance.moudles.Count is 0)
            {
                GUI.Label(new Rect(builder.position.width / 2 - 50, builder.position.height / 2 - 10, 100, 20), "Empty Data!");
                return;
            }

            for (int i = 0; i < PackageBuilderSetting.instance.moudles.Count; i++)
            {
                if (PackageBuilderSetting.instance.moudles[i] is null || PackageBuilderSetting.instance.moudles[i].root is null)
                {
                    PackageBuilderSetting.instance.moudles.Remove(PackageBuilderSetting.instance.moudles[i]);
                    builder.Repaint();
                    return;
                }

                Object target = PackageBuilderSetting.instance.moudles[i].root;
                if (foldouts.ContainsKey(target.name) is false)
                {
                    foldouts[target.name] = false;
                }

                PackageBuilderSetting.instance.moudles[i].root = DrawingAssetObjectItem(target, true, true, 0, builder.position.width - 40);
                if (foldouts[target.name] is false)
                {
                    continue;
                }

                GUI.enabled = false;
                string[] packages = AssetDatabase.GetSubFolders(AssetDatabase.GetAssetPath(target));
                for (int j = 0; j < packages.Length; j++)
                {
                    DrawingPackageInfo(AssetDatabase.LoadAssetAtPath<Object>(packages[j]), 20, builder.position.width - 60);
                }

                GUI.enabled = true;
            }
        }

        private void DrawingPackageInfo(Object moudle, float offset, float width)
        {
            if (foldouts.ContainsKey(moudle.name) is false)
            {
                foldouts[moudle.name] = false;
            }

            DrawingAssetObjectItem(moudle, true, false, offset, width);

            if (foldouts[moudle.name])
            {
                string[] packages = AssetDatabase.GetSubFolders(AssetDatabase.GetAssetPath(moudle));
                for (int j = 0; j < packages.Length; j++)
                {
                    DrawingPackageInfo(AssetDatabase.LoadAssetAtPath<Object>(packages[j]), offset + 20, width - 20);
                }

                string[] files = Directory.GetFiles(AssetDatabase.GetAssetPath(moudle));

                for (int i = 0; i < files.Length; i++)
                {
                    if (files[i].EndsWith(".meta"))
                    {
                        continue;
                    }

                    DrawingAssetObjectItem(AssetDatabase.LoadAssetAtPath<Object>(files[i]), false, false, offset + 20, width - 20);
                }
            }
        }

        private Object DrawingAssetObjectItem(Object target, bool isShowFoldout, bool active, float offset, float width)
        {
            if (target is null)
            {
                return null;
            }

            GUILayout.BeginHorizontal();
            GUILayout.Space(offset);
            if (foldouts.ContainsKey(target.name) is false)
            {
                foldouts[target.name] = false;
            }

            if (isShowFoldout)
            {
                GUI.enabled = isShowFoldout;
                foldouts[target.name] = EditorGUILayout.Toggle(foldouts[target.name], EditorStyles.foldout, GUILayout.Width(17));
                GUI.enabled = active;
                target = EditorGUILayout.ObjectField(target, typeof(Object), false, GUILayout.Width(width));
                GUI.enabled = false;
            }
            else
            {
                GUILayout.Space(20);
                target = EditorGUILayout.ObjectField(target, typeof(Object), false, GUILayout.Width(width));
            }

            GUILayout.EndHorizontal();
            return target;
        }

        private void CheckMouseDragEvent(Rect rect)
        {
            if (Rect.zero.Equals(rect) is not true && rect.Contains(UnityEngine.Event.current.mousePosition) is not true)
            {
                return;
            }

            if (UnityEngine.Event.current.type is not EventType.DragUpdated)
            {
                return;
            }

            DragAndDrop.visualMode = DragAndDropVisualMode.Link;
        }

        private void CheckMouseDragdropEvent(Rect rect)
        {
            if (Rect.zero.Equals(rect) is not true && rect.Contains(UnityEngine.Event.current.mousePosition) is not true)
            {
                return;
            }

            if (UnityEngine.Event.current.type is not EventType.DragPerform)
            {
                return;
            }

            if (PackageBuilderSetting.instance.moudles is null)
            {
                PackageBuilderSetting.instance.moudles = new List<ModuleInfo>();
            }

            for (int i = 0; i < DragAndDrop.paths.Length; i++)
            {
                if (Path.GetExtension(DragAndDrop.paths[i]).IsNullOrEmpty() is false)
                {
                    continue;
                }

                PackageBuilderSetting.instance.moudles.Add(new ModuleInfo()
                {
                    root = DragAndDrop.objectReferences[i],
                    version = 0,
                });
            }

            PackageBuilderSetting.Save();
        }

        public void Build(params ModuleInfo[] moudles)
        {
            if (moudles is null || moudles.Length is 0)
            {
                return;
            }

            CompileHotfixDll(moudles);
            List<AssetBundleBuild> builds = GenerateBuildList(moudles);
            string outpath = AssetBuilderSetting.instance.options.output + "/" + AppConfig.GetPlatformName().ToLower();
            if (!Directory.Exists(outpath))
            {
                Directory.CreateDirectory(outpath);
            }

            builder?.cache.GenericCacheData(moudles);
            AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(outpath, builds.ToArray(), BuildAssetBundleOptions.None, EditorUserBuildSettings.activeBuildTarget);
            string output = $"{AssetBuilderSetting.instance.options.output}/{AppConfig.GetPlatformName().ToLower()}";
            foreach (var module in moudles)
            {
                List<PackageInfo> datables = GetAssetPackages(module, output, manifest);
                File.WriteAllText(AssetBuilderSetting.GetOutputFilePath(AppConfig.GetModuleFileListName(module.root)), JsonConvert.SerializeObject(datables));
            }

            PackageBuilderSetting.Save();
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();

            if (!EditorUtility.DisplayDialog("Tips", "打包完成！是否上传到资源服务器？", "是", "否"))
            {
                return;
            }

            if (AssetBuilderSetting.instance.uploads == null || AssetBuilderSetting.instance.uploads.Count <= 0 || AssetBuilderSetting.instance.uploads.Where(x => x.isOn).Count() == 0)
            {
                EditorUtility.DisplayDialog("ERROR", "找不到资源服务器配置信息！", "确定");
                return;
            }


            UploadFileList uploadFileList = new UploadFileList();
            for (int i = 0; i < AssetBuilderSetting.instance.uploads.Count; i++)
            {
                UploadConfig config = AssetBuilderSetting.instance.uploads[i];
                if (AssetBuilderSetting.instance.uploads[i].isOn is false)
                {
                    continue;
                }

                switch (config.service)
                {
                    case Service.OSS:
                        OssClient oss = new OssClient(config.url, config.pid, config.sid);
                        try
                        {
                            var exist = oss.DoesBucketExist(config.name);
                            if (exist is false)
                            {
                                var request = new CreateBucketRequest(config.name);
                                request.ACL = CannedAccessControlList.PublicRead;
                                request.DataRedundancyType = DataRedundancyType.ZRS;
                                Bucket bucket = oss.CreateBucket(request);
                                Client.Console.WriteLine(bucket.Name);
                            }

                            Client.Console.WriteLineFormat("Check object Exist succeeded");
                            Client.Console.WriteLineFormat("exist ? {0}", exist);
                        }
                        catch (Exception ex)
                        {
                            Client.Console.WriteLineFormat("Check object Exist failed. {0}", ex.Message);
                        }


                        break;
                }

                for (int j = 0; j < builds.Count; j++)
                {
                    uploadFileList.Upload(config, AssetBuilderSetting.GetOutputFilePath(builds[j].assetBundleName));
                }

                for (int j = 0; j < moudles.Length; j++)
                {
                    uploadFileList.Upload(config, AssetBuilderSetting.GetOutputFilePath(AppConfig.GetModuleFileListName(moudles[j].root)));
                }
            }

            uploadFileList.completionEvent += () =>
            {
                EditorUtility.ClearProgressBar();
                EditorUtility.DisplayDialog("Info", "Builder Assets Completion!", "OK");
            };
            uploadFileList.progressEvent += args => { EditorUtility.DisplayProgressBar("Upload", "it Uploading Files,please waiting.", args); };
            uploadFileList.OnStart();
        }

        private void CompileHotfixDll(params ModuleInfo[] moudles)
        {
            CompileDllCommand.CompileDllActiveBuildTarget();
            if (AppConfig.instance.hotfixOptions.aotList is not null && AppConfig.instance.hotfixOptions.aotList.Count is not 0)
            {
                foreach (var dllName in AppConfig.instance.hotfixOptions.aotList)
                {
                    foreach (var module in moudles)
                    {
                        string destDir = AssetDatabase.GetAssetPath(module.root) + "/scripts";
                        if (Directory.Exists(destDir) is false)
                        {
                            Directory.CreateDirectory(destDir);
                        }

                        string scrPath = $"{SettingsUtil.AssembliesPostIl2CppStripDir}/{EditorUserBuildSettings.activeBuildTarget}/{dllName}";
                        Client.Console.WriteLine(scrPath);
                        if (File.Exists(scrPath))
                        {
                            File.Copy(scrPath, $"{destDir}/{dllName}.bytes", true);
                        }
                    }
                }
            }

            Client.Console.WriteLine(string.Join(",", AppConfig.instance.hotfixOptions.aotList));
            if (SettingsUtil.AOTAssemblyNames is not null && SettingsUtil.AOTAssemblyNames.Count > 0)
            {
                foreach (var dllName in SettingsUtil.AOTAssemblyNames)
                {
                    foreach (var module in moudles)
                    {
                        string destDir = AssetDatabase.GetAssetPath(module.root) + "/scripts";
                        if (Directory.Exists(destDir) is false)
                        {
                            Directory.CreateDirectory(destDir);
                        }

                        string scrPath = $"{SettingsUtil.AssembliesPostIl2CppStripDir}/{EditorUserBuildSettings.activeBuildTarget}/{dllName}";
                        if (File.Exists(scrPath))
                        {
                            File.Copy(scrPath, $"{destDir}/{dllName}.bytes", true);
                        }
                    }
                }
            }

            Client.Console.WriteLine(string.Join(",", SettingsUtil.HotUpdateAssemblyFilesExcludePreserved));
            foreach (var item in SettingsUtil.HotUpdateAssemblyFilesExcludePreserved)
            {
                foreach (var module in moudles)
                {
                    string destDir = AssetDatabase.GetAssetPath(module.root) + "/scripts";
                    if (Directory.Exists(destDir) is false)
                    {
                        Directory.CreateDirectory(destDir);
                    }

                    string hotfix = $"{SettingsUtil.HotUpdateDllsRootOutputDir}/{EditorUserBuildSettings.activeBuildTarget}/{item}";
                    if (File.Exists(hotfix))
                    {
                        File.Copy(hotfix, $"{destDir}/{item}.bytes", true);
                    }
                }
            }

            Client.Console.WriteLine(string.Join(",", SettingsUtil.HotUpdateAssemblyFilesExcludePreserved));
            AssetDatabase.SaveAssets();
            AssetDatabase.Refresh();
        }

        private List<PackageInfo> GetAssetPackages(ModuleInfo module, string output, AssetBundleManifest manifest)
        {
            string[] dirs = AssetDatabase.GetSubFolders(AssetDatabase.GetAssetPath(module.root));
            List<PackageInfo> datables = new List<PackageInfo>();
            ++module.version;
            foreach (string dir in dirs)
            {
                Object packObject = AssetDatabase.LoadAssetAtPath<Object>(dir);
                string name = AppConfig.GetFileName($"{module.root.name.ToLower()}_{packObject.name.ToLower()}");
                string packagePath = $"{output}/{name}";
                if (File.Exists(packagePath) is false)
                {
                    continue;
                }

                PackageInfo packageInfo = new()
                {
                    dependencies = new List<string>()
                };
                packageInfo.version = module.version;
                packageInfo.name = AppConfig.GetFileName($"{module.root.name.ToLower()}_{packObject.name.ToLower()}");
                packageInfo.length = new FileInfo($"{output}/{packageInfo.name}").Length;
                packageInfo.dependencies.AddRange(manifest.GetAllDependencies(packageInfo.name));
                packageInfo.files = new List<string>();
                packageInfo.files.AddRange(GetFileList(packObject));
                packageInfo.module = module.root.name.ToLower();
                datables.Add(packageInfo);
            }

            return datables;
        }

        private string[] GetFileList(Object target)
        {
            string[] files = Directory.GetFiles(AssetDatabase.GetAssetPath(target), "*.*", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                files[i] = files[i].NormalPath();
            }

            return files.Where(x => x.EndsWith(".meta") is false).ToArray();
        }

        private List<AssetBundleBuild> GenerateBuildList(params ModuleInfo[] modules)
        {
            List<AssetBundleBuild> builds = new List<AssetBundleBuild>();
            foreach (var module in modules)
            {
                string[] dirList = AssetDatabase.GetSubFolders(AssetDatabase.GetAssetPath(module.root));
                foreach (var dir in dirList)
                {
                    Object packaged = AssetDatabase.LoadAssetAtPath<Object>(dir);
                    AssetBundleBuild assetBundleBuild = new AssetBundleBuild();
                    assetBundleBuild.assetBundleName = AppConfig.GetFileName($"{module.root.name.ToLower()}_{packaged.name.ToLower()}");
                    assetBundleBuild.assetNames = GetFileList(packaged);
                    builds.Add(assetBundleBuild);
                }
            }

            return builds;
        }
    }

    public class PackageCacheWindow
    {
        private PackageBuilderEditorWindow window;
        private Dictionary<string, bool> foldouts = new Dictionary<string, bool>();

        public PackageCacheWindow(PackageBuilderEditorWindow window)
        {
            this.window = window;
        }

        public void OnGUI()
        {
            if (PackageBuilderSetting.instance.moudles is null || PackageBuilderSetting.instance.moudles.Count is 0)
            {
                return;
            }

            for (int i = 0; i < PackageBuilderSetting.instance.moudles.Count; i++)
            {
                ModuleInfo module = PackageBuilderSetting.instance.moudles[i];
                if (!foldouts.TryGetValue(module.root.name, out bool state))
                {
                    foldouts.Add(module.root.name, false);
                }

                foldouts[module.root.name] = EditorGUILayout.Foldout(state, module.root.name, EditorStyles.foldoutHeader);
                if (foldouts[module.root.name])
                {
                    string m = $"{AssetBuilderSetting.instance.options.cachePath}/{module.root.name}";
                    if (Directory.Exists(m) is false)
                    {
                        Directory.CreateDirectory(m);
                    }

                    string[] dirList = Directory.GetDirectories(m);
                    foreach (string dir in dirList)
                    {
                        string version = new DirectoryInfo(dir).Name;
                        if (GUILayout.Button(version))
                        {
                            if (EditorUtility.DisplayDialog("Tips", $"Do you want to rollback the resource version to {version}", "OK", "Cancel"))
                            {
                                RollbackVersion(module, int.Parse(version));
                            }
                        }
                    }
                }
            }
        }

        public void GenericCacheData(params ModuleInfo[] moudles)
        {
            foreach (var item in moudles)
            {
                string dirPath = AssetBuilderSetting.instance.options.cachePath + "/" + item.root.name;
                if (Directory.Exists(dirPath) is false)
                {
                    Directory.CreateDirectory(dirPath);
                }

                List<int> dirList = Directory.GetDirectories(dirPath).Select(x => int.Parse(new DirectoryInfo(x).Name)).ToList();
                if (dirList.Count >= AssetBuilderSetting.instance.options.maxCacheCount)
                {
                    dirList.Sort();
                    int delCount = dirList.Count - AssetBuilderSetting.instance.options.maxCacheCount;
                    for (int i = 0; i < delCount; i++)
                    {
                        Directory.Delete($"{dirPath}/{dirList.First()}", true);
                    }
                }

                string cachePath = $"{dirPath}/{item.version}";
                if (!Directory.Exists(cachePath))
                {
                    Directory.CreateDirectory(cachePath);
                }

                string[] packages = AssetDatabase.GetSubFolders(AssetDatabase.GetAssetPath(item.root));
                if (packages is not null && packages.Length is not 0)
                {
                    foreach (var bundle in packages)
                    {
                        string filePath = $"{item.root.name.ToLower()}_{bundle.Substring(bundle.LastIndexOf("/") + 1).ToLower()}";
                        string scr = AssetBuilderSetting.GetOutputFilePath(AppConfig.GetFileName(filePath));
                        if (File.Exists(scr))
                        {
                            File.Copy(scr, $"{cachePath}/{Path.GetFileName(scr)}", true);
                        }
                    }
                }
            }
        }

        private void RollbackVersion(ModuleInfo module, int version)
        {
        }
    }
}